﻿namespace LivePatterns.Tests
{
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Reflection;

    [TestClass]
    public class BuildTests
    {
        [TestMethod]
        public void Implement()
        {
            ITest implementation = Build.Implement<ITest>();
            Assert.AreEqual(default(int), implementation.IntValue);

            implementation.IntValue = 5;
            Assert.AreEqual(5, implementation.IntValue);
        }

        [TestMethod]
        public void Snapshot()
        {
            ITest instance = new TestImpl();
            instance.IntValue = 5;

            ITest snapshot = Build.Snapshot<ITest>(instance);
            instance.IntValue = 10;
            Assert.AreEqual(5, snapshot.IntValue);
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void Snapshot_SetProperty()
        {
            ITest instance = new TestImpl();
            ITest snapshot = Build.Snapshot<ITest>(instance);
            snapshot.IntValue = 2;
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void Snapshot_InvokeMethod()
        {
            ITest instance = new TestImpl();
            ITest snapshot = Build.Snapshot<ITest>(instance);
            snapshot.Do();
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void Snapshot_SubscribeEvent()
        {
            ITest instance = new TestImpl();
            ITest snapshot = Build.Snapshot<ITest>(instance);
            snapshot.Event += (s, e) => { };
        }

        [TestMethod]
        public void NotifyPropertyChanged()
        {
            ITest instance = new TestImpl();
            ITest notify = Build.NotifyPropertyChanged<ITest>(instance);
            Assert.IsTrue(notify is INotifyPropertyChanged);

            INotifyPropertyChanged inpc = notify as INotifyPropertyChanged;

            PropertyChangedEventHandler checkChanged = (s, e) =>
            {
                Assert.AreEqual("IntValue", e.PropertyName);
            };
            inpc.PropertyChanged += checkChanged;
            notify.IntValue = 8;
            inpc.PropertyChanged -= checkChanged;

            PropertyChangedEventHandler checkNotChanged = (s, e) =>
            {
                Assert.Fail();
            };
            inpc.PropertyChanged += checkNotChanged;
            notify.IntValue = 8;
            inpc.PropertyChanged -= checkNotChanged;
        }

        [TestMethod]
        public void Multicast()
        {
            ITest[] targets = new ITest[] { new TestImpl(), new TestImpl() };
            ITest multicast = Build.Multicast<ITest>(targets);

            multicast.IntValue = 8;
            Assert.IsTrue(targets.All(t => t.IntValue == 8));

            multicast.Do();
            Assert.IsTrue(targets.All(t => t.IntValue == 20));
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void Multicast_GetProperty()
        {
            ITest[] targets = new ITest[] { new TestImpl(), new TestImpl() };
            ITest multicast = Build.Multicast<ITest>(targets);

            int t = multicast.IntValue;
        }

        [TestMethod]
        public void LoadBalance()
        {
            ITest[] targets = new ITest[] { new TestImpl(), new TestImpl() };
            ITest loadBalancer = Build.LoadBalance<ITest>(targets);

            loadBalancer.Do();
            Assert.AreEqual(20, targets[0].IntValue);
            Assert.AreEqual(default(int), targets[1].IntValue);

            loadBalancer.Do();
            Assert.AreEqual(20, targets[0].IntValue);
            Assert.AreEqual(20, targets[1].IntValue);
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void LoadBalance_SetProperty()
        {
            ITest[] targets = new ITest[] { new TestImpl(), new TestImpl() };
            ITest loadBalancer = Build.LoadBalance<ITest>(targets);

            loadBalancer.IntValue = 0;
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void LoadBalance_SubscribeEvent()
        {
            ITest[] targets = new ITest[] { new TestImpl(), new TestImpl() };
            ITest loadBalancer = Build.LoadBalance<ITest>(targets);

            loadBalancer.Event += (s, e) => { };
        }

        [TestMethod]
        public void Override()
        {
            bool changed = false;
            ITest target = new TestImpl();
            ITest overriden = Build.Override<ITest>(target, new Dictionary<MethodBase, Delegate>()
			{
				{ typeof(ITest).GetMethod("Do"), new Action(() => { changed = true; }) }
			});

            overriden.Do();
            Assert.IsTrue(changed);
            Assert.AreEqual(default(int), overriden.IntValue);
        }


        public interface ITestParent
        {
            string StringValue { get; }
        }

        /// <remarks>If this interface would not be public, the type builders could not access it.</remarks>
        public interface ITest : ITestParent
        {
            int IntValue { get; set; }

            void Do();

            event EventHandler Event;
        }

        private class TestImpl : ITest
        {
            public int IntValue { get; set; }

            public string StringValue { get { return "Hello world!"; } }

            public void Do()
            {
                this.IntValue = 20;
            }

            public event EventHandler Event;
        }
    }
}
